package com.zf.until_now;

import java.text.MessageFormat;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.concurrent.*;
import java.util.stream.Collectors;

/**
 * 虚拟线程
 */
public class VirtualThreadTest {

    public static void main(String[] args) throws InterruptedException {

        // 启动一个虚拟线程的方式
        // testStartVirtualThreadMethod();

        // 创建100个虚拟线程，统计一下运行虚拟线程时 阻塞前和恢复运行后，挂载到的平台线程是否发送变化
        // testVirtualThreadMountedPlatformThreadChange();

        // 创建100000个虚拟线程，统计一下运行虚拟线程时，使用到了多少个平台线程
        // testUsedPlatformThreadNumWhenRunVirtualThread();

        // 比较 虚拟线程 和 平台线程 运行时间
        compareVirtualThreadAndPlatformThreadTime();

    }


    /**
     * 启动一个虚拟线程的方式
     */
    public static void testStartVirtualThreadMethod() throws InterruptedException {
        // 模拟任务
        Runnable task = () -> {
            // 打印线程的详细信息
            System.out.println(Thread.currentThread());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException ignored) {
            }
        };

        System.out.println("方式1：");
        // 1、使用 Thread.startVirtualThread() :
        Thread t1 = Thread.startVirtualThread(task);
        t1.join();


        System.out.println("方式2：");
        // 2、使用 Thread.ofVirtual().start() :
        Thread.Builder.OfVirtual ofVirtual = Thread.ofVirtual();
        ofVirtual.name("OfVirtual方式");
        Thread t2 = ofVirtual.start(task);
        t2.join();
        Thread t3 = ofVirtual.start(task);
        t3.join();


        System.out.println("方式3：");
        // 3、使用线程池：
        // Executors.newVirtualThreadPerTaskExecutor() 线程池 会给提交的每一个任务 都 创建一个全新的虚拟线程
        ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
        for (int i = 0; i < 5; i++) {
            executor.submit(task);
        }
        // 关闭线程池，等待已经提交的任务全部执行结束
        executor.close();


        System.out.println("方式4：");
        // 4、使用虚拟线程工厂:
        ThreadFactory virtualThreadFactory = Thread.ofVirtual()
                .name("虚拟线程工厂", 0)
                .factory();
        Thread ft1 = virtualThreadFactory.newThread(task);
        Thread ft2 = virtualThreadFactory.newThread(task);
        ft1.start();
        ft1.join();
        System.out.println("join1 end");
        ft2.start();
        ft2.join();
        System.out.println("join2 end");
    }


    /**
     * 创建100个虚拟线程，统计一下运行虚拟线程时 阻塞前和恢复运行后，挂载到的平台线程是否发送变化
     */
    public static void testVirtualThreadMountedPlatformThreadChange() {
        // 记录使用了多少个平台线程
        ConcurrentHashMap<String, LinkedHashSet<String>> ptMap = new ConcurrentHashMap<>();

        // 创建虚拟线程的线程池
        ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
        // 创建100个虚拟线程
        for (int i = 0; i < 100; i++) {
            // 提交任务，创建虚拟线程
            executor.submit(() -> {
                // 阻塞前挂载的平台线程
                // 格式：VirtualThread[#22]/runnable@ForkJoinPool-1-worker-1, @后面是使用的平台线程的名字
                String beforeBlockedName = Thread.currentThread().toString();
                // 虚拟线程的名称
                String vtName = beforeBlockedName.split("@")[0];
                // 平台线程的名称
                String ptName = beforeBlockedName.split("@")[1];

                // 记录平台线程
                LinkedHashSet<String> ptSet = ptMap.computeIfAbsent(vtName, k -> new LinkedHashSet<>(Collections.singletonList(ptName)));

                try {
                    // 阻塞操作
                    Thread.sleep(250);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                // 恢复运行后挂载的平台线程
                String resumedName = Thread.currentThread().toString();
                ptSet.add(resumedName.split("@")[1]);
            });
        }
        // 关闭虚拟线程的线程池，等待已经提交的任务全部执行结束
        executor.close();

        // 打印用到的所有平台线程
        String allPtStr = ptMap.values().stream().flatMap(LinkedHashSet::stream).distinct().sorted().collect(Collectors.joining("\n"));
        System.out.printf("使用的平台线程：%s\n", allPtStr);
        System.out.println("========");

        // 打印挂载的平台线程发生变化的虚拟线程
        for (Map.Entry<String, LinkedHashSet<String>> entry : ptMap.entrySet()) {
            LinkedHashSet<String> ptSet = entry.getValue();
            // 输出阻塞前后挂载的平台线程发生变化的虚拟线程
            if (ptSet.size() > 1) {
                String changeStr = String.join(" -> ", ptSet);
                System.out.println(MessageFormat.format("虚拟线程: {0}, 阻塞前和恢复运行后挂载的平台线程：{1}", entry.getKey(), changeStr));
            }
        }

    }


    /**
     * 创建100000个虚拟线程，统计一下运行虚拟线程时，使用到了多少个平台线程
     */
    public static void testUsedPlatformThreadNumWhenRunVirtualThread() {
        // 记录使用了多少个平台线程
        CopyOnWriteArraySet<String> ptSet = new CopyOnWriteArraySet<>();

        // 创建虚拟线程的线程池
        ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
        long startTime = System.currentTimeMillis();
        // 创建100000个虚拟线程
        for (int i = 0; i < 100000; i++) {
            // 提交任务，创建虚拟线程
            executor.submit(() -> {
                // 格式：VirtualThread[#22]/runnable@ForkJoinPool-1-worker-1，@后面是使用的平台线程的名字
                String beforeBlocked = Thread.currentThread().toString();
                // 记录阻塞前挂载到的平台线程
                ptSet.add(beforeBlocked.split("@")[1]);
                try {
                    // 阻塞操作
                    Thread.sleep(250);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                // 记录恢复运行后挂载到的平台线程
                String resumed = Thread.currentThread().toString();
                ptSet.add(resumed.split("@")[1]);
            });
        }
        // 关闭虚拟线程的线程池，等待已经提交的任务全部执行结束
        executor.close();

        long endTime = System.currentTimeMillis();
        System.out.printf("总耗时：%dms\n", endTime - startTime);

        // 打印用到的所有平台线程
        String allPtStr = ptSet.stream().sorted().collect(Collectors.joining("\n"));
        System.out.printf("使用的平台线程：\n%s\n", allPtStr);

    }


    /**
     * 比较 虚拟线程 和 平台线程 运行时间
     */
    public static void compareVirtualThreadAndPlatformThreadTime() {
        // 模拟10000个请求
        int requestNum = 10000;
        long vt = 0, p1 = 0, p2 = 0, p3 = 0, p4 = 0;

        // 三轮测试取平均值
        for (int i = 1; i <= 3; i++) {
            System.out.println("第 " + i + " 轮测试：");
            vt += testVirtualThreadTime(requestNum);
            p1 += testPlatformThreadTime(requestNum, 200);
            p2 += testPlatformThreadTime(requestNum, 400);
            p3 += testPlatformThreadTime(requestNum, 600);
            p4 += testPlatformThreadTime(requestNum, 800);
        }
        System.out.println("3 轮测试结束：");
        System.out.printf("虚拟线程平均耗时：%dms\n", vt / 3);
        System.out.printf("200个平台线程平均耗时：%dms\n", p1 / 3);
        System.out.printf("400个平台线程平均耗时：%dms\n", p2 / 3);
        System.out.printf("600个平台线程平均耗时：%dms\n", p3 / 3);
        System.out.printf("800个平台线程平均耗时：%dms\n", p4 / 3);
    }


    /**
     * 虚拟线程
     */
    public static long testVirtualThreadTime(int requestNum) {
        // 使用线程池创建虚拟线程，这个线程池会给每一个人物创建一个虚拟线程
        ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
        long start = System.currentTimeMillis();
        for (int i = 0; i < requestNum; i++) {
            executor.execute(VirtualThreadTest::handle);
        }
        // 关闭线程池，等待已经提交的任务全部执行结束
        executor.close();
        long end = System.currentTimeMillis();
        long useTime = end - start;
        System.out.printf("虚拟线程耗时：%dms\n", useTime);
        return useTime;
    }

    /**
     * 平台线程
     */
    public static long testPlatformThreadTime(int requestNum, int poolSize) {
        // 固定大小线程池
        ExecutorService executor = Executors.newFixedThreadPool(poolSize);
        long start = System.currentTimeMillis();
        for (int i = 0; i < requestNum; i++) {
            executor.execute(VirtualThreadTest::handle);
        }
        // 关闭线程池，等待已经提交的任务全部执行结束
        executor.close();
        long end = System.currentTimeMillis();
        long useTime = end - start;
        System.out.printf("%d个平台线程耗时：%dms\n", poolSize, useTime);
        return useTime;
    }

    public static void handle() {
        try {
            // 模拟任务处理，每个任务耗时250ms
            Thread.sleep(250);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}
